<template>
  {{ computedRef }}
  <div>
    <input type="text" v-model="title" @keydown.enter="addTodo" />
    <button v-if="active > 0" @click="clear">清理</button>
    <ul>
      <li v-for="todo in todos">
        <input type="checkbox" v-model="todo.done" />
        <span :class="{ done: todo.done }"> {{ todo.title }}</span>
      </li>
    </ul>
    <div>
      全选<input type="checkbox" v-model="allDone" />
      <span> {{ active }} / {{ all }} </span>
    </div>
  </div>
  <hr />
  <button @click="handleWatchEffectClick">watchEffect的停止监听</button>

  <h2 ref="titleName">标题</h2>
  <div>
    <button
      id="aa"
      @click="
        r++;
        s++;
      "
    >
      {{ r }} - {{ s }}
    </button>
  </div>
  <!-- <div v-for="item in obj.list">{{ item }}</div> -->
</template>

<script setup>
import { ref, computed, watch, reactive, watchEffect, onMounted } from "vue";
const title = ref("");
const todos = ref([
  { title: "吃饭", done: false },
  { title: "睡觉", done: true },
]);
const addTodo = () => {
  if (!title.value) return;
  todos.value.push({ title: title.value, done: false });
  title.value = "";
};
let active = computed(() => todos.value.filter((v) => v.done).length);
let all = computed(() => todos.value.length);
let allDone = computed({
  get() {
    //todos.value.length&&active.value===todos.value.length
    return todos.value.length && todos.value.every((item) => item.done);
  },
  set(value) {
    todos.value.forEach((item) => {
      item.done = value;
    });
  },
});
let clear = () => {
  todos.value = todos.value.filter((v) => !v.done);
};
//watchEffect 也可以放在生命周期函数内使用
const stop1 = watchEffect((onInvalidate) => {
  // const CancelToken = axios.CancelToken;
  // const source = CancelToken.source();
  // onInvalidate(() => {
  //   source.cancel();
  // });
  const timer = setTimeout(() => {
    console.log("2s后执行");
  }, 1000);
  onInvalidate(() => {
    clearTimeout(timer);
    console.log(5666);
  }); //清除副作用，可以实现防抖的功能
}); //stop1是一个停止监听函数，调用就不会在执行watchEffect中的函数
const number = ref(0);
const stop2 = watch(
  number,
  (newVal, oVal) => {
    console.log(newVal, oVal, "stop2");
  },
  { immediate: true }
); //stop2是一个停止监听函数，调用就不会在执行watch中的函数

/*
(总结非常重要)下面例子如果看不懂记住这个总结用就是了
watch用法
监听对象和数组如果想要方便省事都监听() => JSON.parse(JSON.stringify(obj))(reactive定义的obj是数组和对象都可以)或() => JSON.parse(JSON.stringify(obj.value))(ref定义的obj)
const obj1= reactive({value:0}) const obj2=ref(0)
监听一个响应式对象中基本类型数据的写法，reactive定义的对象必须用函数的形式() => obj1.value，
ref定义的对象直接obj2,监听基本类型数据不需要加deep:true
监听响应式的对象或数组解构出来的普通对象或数组也需要用函数的形式
不分响应式数组和普通数组,监听数组就用() => JSON.parse(JSON.stringify(array))
const obj= reactive({
  a:100,
  b:{c:20}
})
监听reactive对象情况:监听reactive定义的obj(注意新值和旧值相同)或() => JSON.parse(JSON.stringify(obj))(注意新值和旧值不相同)
但是不能直接obj={}这样修改,监听不到,如果需要删除obj中的属性,用这种方式for(const key in obj){delete obj[key]}可以监听到,
直接修改obj中属性的值也都可以监听到

const obj= ref({
  a:100,
  b:{c:20}
})
监听ref对象情况:直接监听ref定义的obj(注意新值和旧值相同)或() => JSON.parse(JSON.stringify(obj.value))(注意新值和旧值不相同)
如果需要删除obj中的属性可以直接这样修改obj.value={}可以监听到,直接修改obj中属性的值(监听obj需要加deep:true,
监听() => JSON.parse(JSON.stringify(obj.value))不需要加deep:true)也可以监听到
watchEffect用法
watchEffect只有响应式对象的属性被赋值的时候watchEffect才会再次执行,
如果这个属性的值是对象或数组,修改这个对象的属性或用数组的方法修改数组,watchEffect都不会再次执行,
*/

let obj = reactive({
  value: 0,
  list: [1, 2, 3],
  a: {
    b: 100,
  },
});
const tents = ref([{ name: "wang", age: 20, men: [{ tema: 100 }] }]);
const objaccont = ref({
  name: "xiao",
  age: [1, 2, 3],
});
/*(重要) 监听一个响应式对象中基本类型数据的写法，必须用函数的形式() => obj.value，
监听响应式的对象或数组解构出来的普通对象或数组也需要用函数的形式*/
watch(
  () => obj.value,
  (newVal, oldVal) => {
    console.log(newVal, oldVal);
  }
);
//监听多个数据的写法
watch(
  [obj, () => JSON.parse(JSON.stringify(obj))],
  ([newVal1, newVal2], [oVal1, oVal2]) => {
    console.log(newVal1, newVal2, oVal1, oVal2, "321");
  }
);
/*(重要) watchEffect收集依赖只有数组被重新赋值的时候obj.list=[5],watchEffect会再此执行,
其他改变数组的方式都不会再次执行,因为watchEffect收集的是list属性的依赖*/
watchEffect(() => {
  console.log(obj.list, "789550000000000");
});
/*(重要)watchEffect收集依赖只有objaccont.value被赋值的时候objaccont.value={},watchEffect才会从新执行,
修改ref对象内部属性的值不会被执行,因为watchEffect收集的是value属性的依赖*/
watchEffect(() => {
  console.log(objaccont.value, "9850000");
});
watch(
  () => JSON.parse(JSON.stringify(objaccont.value)),
  (newVal, oVal) => {
    console.log(newVal, oVal, "objaccont000000000000000");
  }
);
watch(
  objaccont,
  (newVal, oVal) => {
    console.log(newVal, oVal, "objaccont111111111");
  },
  { deep: true }
);
/*(重要) 如果希望监听普通对象并且newVal和oVal的数据不一样推荐监听这样写() => JSON.parse(JSON.stringify(obj)),
(这种方式不常用，因为大部分开发场景中不需要拿到旧的值，除非你想要拿到新值和旧值不一样的数据)
但是一般情况下用法都是直接监听响应式对象obj(注意这时候新值和旧值的数据是一样的)*/
watch(
  () => JSON.parse(JSON.stringify(obj)),
  (newVal, oVal) => {
    console.log(newVal, oVal, "deep12222222000000000000");
  }
);
watch(obj, (newVal, oVal) => {
  console.log(newVal, oVal, "deep222222222222");
});

/*(重要)
只要监听数组就用() => JSON.parse(JSON.stringify(tents.value))这种方式，一劳永逸不会出错，并且能够拿到修改前后的不同的值*/
watch(
  () => JSON.parse(JSON.stringify(tents.value)),
  (newVal, oVal) => {
    console.log(newVal, oVal, "0123000000099999999999");
  } //不需要加deep:true
);
/*(重要) watch监听响应式数组 当obj.list被赋值如obj.list=[]监听不到，加deep:true也监听不到，
其他修改数组的方式不加deep:true也都能监听到，监听数组推荐watch监听() => JSON.parse(JSON.stringify)*/
// watch(obj.list, (newVal, oVal) => {
//   console.log(newVal, oVal, "21000000");
// });
const attrse = ref({});
//推荐直接监听attrse(常用)
watch(attrse, (newVal, oVal) => {
  console.log(newVal, oVal, "8888888888");
});
const handleWatchEffectClick = () => {
  //objaccont.value.age = "5622";
  //objaccont.value.age=[];
  //objaccont.value.age.length = 0;
  //objaccont.value.age.reverse();
  //objaccont.value.age.push(633);
  //objaccont.value.ww = 200;
  objaccont.value.ww = [6, 2, 3];
  //objaccont.value = {};
  //tents.value.push(11)
  //tents.value.length=0
  //tents.value = [];
  //delete tents.value[0].age
  //tents.value[0].ten={a:1}
  //tents.value[0].men[0].tema = 200;
  //obj.value=200
  //obj.a.b = 300;
  //delete obj.a.b
  //obj.a.age = 1000;
  //obj.a={}
  //delete obj.a
  //obj = {};
  // for (const key in obj) {
  //   delete obj[key];
  // } //可以删除响应式对象内部的属性
  //obj.list.length = 0; //watch可以监听到，watchEffect监听不到
  //obj.list = []; //watchEffect可以监听到，watch监听不到
  //delete obj.list
  //obj.list[0]=1000 //watch可以监听到，watchEffect监听不到
  //obj.list.push(222);//watch可以监听到，watchEffect监听不到
  //obj.list.reverse()
  //obj = reactive({})和obj = {}效果一样//这样做watch监听() => JSON.parse(JSON.stringify(obj))或obj都不会执行，不推荐直接给obj赋值,推荐直接修改obj中的属性
  //obj.a = reactive({})和obj.a = {}效果一样; //这样做watch监听obj.a不会执行,监听() => ({ ...obj.a })会执行,直接给obj.a赋值推荐watch直接监听obj
  //attrse.value = reactive({})和attrse.value = {}效果一样; //这样做watch监听attrse或() => ({ ...attrse.value })都会执行,推荐直接监听attrse
  //tents.value[0].name = "xiaow";
  //objaccont.value = {};
  //objaccont.value.name = "663";
  //obj.list = [];
  // tents.value.push(23);
  // if (number.value > 3) {
  //   stop1();
  //   stop2();
  // }
};
//同一个生命周期可以写多个，可以写在不同函数里用于封装，一个函数中可以封装响应式数据，computed,watch,watchEffect,生命周期等等，可以用于逻辑复用
onMounted(() => {});
onMounted(() => {});

const titleName = ref(null);
const attributes = ref("开始");
watch(
  titleName,
  (newVal, oVal) => {
    /*(重要) <h2>标题</h2>渲染之前newVal是null,oVal值是undefined
    渲染之后newVal的值是<h2>标题</h2>，类型是"object" oVal值是null*/
    console.log(newVal, oVal, "7798996");
  },
  //{ flush: "post"}
);
watchEffect(
  () => {
    /*(重要) 有flush:'post'，watchEffect只执行一次,titleName.value的值是<h2>标题</h2>，类型是"object"，
    没有watchEffect执行2次,titleName.value的值先是null,再是<h2>标题</h2>，类型是都是"object"*/
    console.log(titleName.value, typeof titleName.value, "4563");
  },
  { flush: "post" }
);
watchEffect(
  () => {
    /*有flush: "post"，watchEffect只执行一次 attributes.value是结束，
    没有watchEffect执行2次,attributes.value的值先是开始,再是结束*/
    console.log("名字被修改了", attributes.value);
  },
  {
    flush: "post",
  }
);
attributes.value = "结束";

let r = ref(2);
let s = ref(10);
onMounted(() => {
  console.log(2300000000);
});
const computedRef = computed(() => {
  console.log(63000000);
  return 2;
});
watch(
  r,
  () => {
    console.log(r.value, s.value, "aaaa"); //初始化的值是2，10 点击按钮之后是3，11
    console.log(
      //(重要) 加flush:"post"点击按钮之后值是3-11，不加点击按钮之后值是2-10 加和不加初始化的值都是null，
      document.querySelector("#aa") && document.querySelector("#aa").innerText
    );
  },
  {
    flush: "post",
    immediate: true,
  }
);
//(重要)执行先后顺序，不带flush属性的watchEffect和带有{ immediate: true }的watch谁写在前面谁先执行>computed>onMounted
//(重要)带有{ immediate: true }的watch>computed>带有flush: "post"的watchEffect>onMounted
//watch和watchEffect,computed的详细区别 请看https://www.jianshu.com/p/c257aad46344或https://blog.csdn.net/m0_51969330/article/details/123673334
</script>

<style scoped>
.done {
  color: gray;
  text-decoration: line-through;
}
</style>
